Package org.python.pydev.editor.actions

Source Code of org.python.pydev.editor.actions.PyOrganizeImports

/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Feb 18, 2005
*
* @author Fabio Zadrozny
*/
package org.python.pydev.editor.actions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.Tuple3;
import org.python.pydev.core.docutils.ImportHandle;
import org.python.pydev.core.docutils.ImportHandle.ImportHandleInfo;
import org.python.pydev.core.docutils.PyImportsHandling;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.docutils.StringUtils;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.plugin.preferences.PydevPrefs;
import org.python.pydev.ui.importsconf.ImportsPreferencesPage;

import com.aptana.shared_core.string.FastStringBuffer;
import com.aptana.shared_core.structure.Tuple;

/**
* @author Fabio Zadrozny
*/
public class PyOrganizeImports extends PyAction {

    /**
     * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
     */
    @SuppressWarnings("unchecked")
    public void run(IAction action) {
        try {
            if (!canModifyEditor()) {
                return;
            }

            PyEdit pyEdit = getPyEdit();

            PySelection ps = new PySelection(pyEdit);
            String endLineDelim = ps.getEndLineDelim();
            final IDocument doc = ps.getDoc();
            DocumentRewriteSession session = null;

            try {
                if (ps.getStartLineIndex() == ps.getEndLineIndex()) {
                    //let's see if someone wants to make a better implementation in another plugin...
                    List<IOrganizeImports> participants = ExtensionHelper
                            .getParticipants(ExtensionHelper.PYDEV_ORGANIZE_IMPORTS);

                    for (IOrganizeImports organizeImports : participants) {
                        if (!organizeImports.beforePerformArrangeImports(ps, pyEdit)) {
                            return;
                        }
                    }

                    session = startWrite(doc);

                    performArrangeImports(doc, endLineDelim, pyEdit.getIndentPrefs().getIndentationString());

                    for (IOrganizeImports organizeImports : participants) {
                        organizeImports.afterPerformArrangeImports(ps, pyEdit);
                    }
                } else {
                    session = startWrite(doc);
                    performSimpleSort(doc, endLineDelim, ps.getStartLineIndex(), ps.getEndLineIndex());
                }
            } finally {
                if (session != null) {
                    endWrite(doc, session);
                }
            }
        } catch (Exception e) {
            Log.log(e);
            beep(e);
        }
    }

    /**
     * Stop a rewrite session
     */
    private void endWrite(IDocument doc, DocumentRewriteSession session) {
        if (doc instanceof IDocumentExtension4) {
            IDocumentExtension4 d = (IDocumentExtension4) doc;
            d.stopRewriteSession(session);
        }
    }

    /**
     * Starts a rewrite session (keep things in a single undo/redo)
     */
    private DocumentRewriteSession startWrite(IDocument doc) {
        if (doc instanceof IDocumentExtension4) {
            IDocumentExtension4 d = (IDocumentExtension4) doc;
            return d.startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED);
        }
        return null;
    }

    /**
     * Actually does the action in the document.
     *
     * @param doc
     * @param endLineDelim
     */
    public static void performArrangeImports(IDocument doc, String endLineDelim, String indentStr) {
        List<Tuple3<Integer, String, ImportHandle>> list = new ArrayList<Tuple3<Integer, String, ImportHandle>>();
        //Gather imports in a structure we can work on.
        PyImportsHandling pyImportsHandling = new PyImportsHandling(doc);
        int firstImport = -1;
        for (ImportHandle imp : pyImportsHandling) {
            list.add(new Tuple3<Integer, String, ImportHandle>(imp.startFoundLine, imp.importFound, imp));

            if (firstImport == -1) {
                firstImport = imp.startFoundLine;
            }
        }

        //check if we had any import
        if (firstImport == -1) {
            return;
        }

        //sort in inverse order (for removal of the string of the document).
        Collections.sort(list, new Comparator<Tuple3<Integer, String, ImportHandle>>() {

            public int compare(Tuple3<Integer, String, ImportHandle> o1, Tuple3<Integer, String, ImportHandle> o2) {
                return o2.o1.compareTo(o1.o1);
            }
        });
        //ok, now we have to delete all lines with imports.
        for (Iterator<Tuple3<Integer, String, ImportHandle>> iter = list.iterator(); iter.hasNext();) {
            Tuple3<Integer, String, ImportHandle> element = iter.next();
            String s = element.o2;
            int i = com.aptana.shared_core.string.StringUtils.countLineBreaks(s);
            while (i >= 0) {
                PySelection.deleteLine(doc, (element.o1).intValue());
                i--;
            }
        }
        Collections.sort(list, new Comparator<Tuple3<Integer, String, ImportHandle>>() {

            public int compare(Tuple3<Integer, String, ImportHandle> o1, Tuple3<Integer, String, ImportHandle> o2) {
                //When it's __future__, it has to appear before the others.
                List<ImportHandleInfo> info1 = o1.o3.getImportInfo();
                List<ImportHandleInfo> info2 = o2.o3.getImportInfo();
                boolean isFuture1 = getIsFuture(info1);
                boolean isFuture2 = getIsFuture(info2);
                if (isFuture1 && !isFuture2) {
                    return -1;
                }
                if (!isFuture1 && isFuture2) {
                    return 1;
                }
                return o1.o2.compareTo(o2.o2);
            }

            private boolean getIsFuture(List<ImportHandleInfo> info1) {
                String from1 = null;
                if (info1.size() > 0) {
                    from1 = info1.get(0).getFromImportStr();
                }
                boolean isFuture = from1 != null && from1.equals("__future__");
                return isFuture;
            }
        });

        firstImport--; //add line after the the specified

        //now, re-add the imports
        FastStringBuffer all = new FastStringBuffer();

        if (!ImportsPreferencesPage.getGroupImports()) {
            //no grouping
            for (Iterator<Tuple3<Integer, String, ImportHandle>> iter = list.iterator(); iter.hasNext();) {
                Tuple3<Integer, String, ImportHandle> element = iter.next();
                all.append(element.o2);
                all.append(endLineDelim);
            }
        } else { //we have to group the imports!

            //import from to the imports that should be grouped given its 'from'
            TreeMap<String, List<ImportHandleInfo>> importsWithFrom = new TreeMap<String, List<ImportHandleInfo>>(
                    new Comparator<String>() {

                        public int compare(String o1, String o2) {
                            Tuple<String, String> splitted1 = StringUtils.splitOnFirst(o1, '.');
                            Tuple<String, String> splitted2 = StringUtils.splitOnFirst(o2, '.');

                            boolean isFuture1 = splitted1.o1.equals("__future__");
                            boolean isFuture2 = splitted2.o1.equals("__future__");

                            if (isFuture1 != isFuture2) {
                                if (isFuture1) {
                                    return -1;
                                }
                                return 1;
                            }

                            return o1.compareTo(o2);
                        }
                    });
            List<ImportHandleInfo> importsWithoutFrom = new ArrayList<ImportHandleInfo>();

            fillImportStructures(list, importsWithFrom, importsWithoutFrom);

            //preferences for multiline imports
            boolean multilineImports = ImportsPreferencesPage.getMultilineImports();
            int maxCols = getMaxCols(multilineImports);

            //preferences for how to break imports
            boolean breakWithParenthesis = getBreakImportsWithParenthesis();

            Set<Entry<String, List<ImportHandleInfo>>> entrySet = importsWithFrom.entrySet();
            FastStringBuffer lastFromXXXImportWritten = new FastStringBuffer();
            FastStringBuffer line = new FastStringBuffer();

            for (Entry<String, List<ImportHandleInfo>> entry : entrySet) {

                //first, reorganize them in the order to be written (the ones with comments after the ones without)
                ArrayList<Tuple<String, String>> importsAndComments = new ArrayList<Tuple<String, String>>();
                ArrayList<Tuple<String, String>> importsAndNoComments = new ArrayList<Tuple<String, String>>();

                fillImportFromInfo(entry, importsAndComments, importsAndNoComments);

                //ok, it's all filled, let's start rewriting it!
                boolean firstInLine = true;
                line.clear();
                boolean addedParenForLine = false;

                //ok, write all the ones with comments after the ones without any comments (each one with comment
                //will be written as a new import)
                importsAndNoComments.addAll(importsAndComments);
                for (int i = 0; i < importsAndNoComments.size(); i++) {

                    Tuple<String, String> tuple = importsAndNoComments.get(i);

                    if (firstInLine) {
                        lastFromXXXImportWritten.clear();
                        lastFromXXXImportWritten.append("from ");
                        lastFromXXXImportWritten.append(entry.getKey());
                        lastFromXXXImportWritten.append(" import ");
                        line.append(lastFromXXXImportWritten);
                    } else {
                        line.append(", ");
                    }

                    if (multilineImports) {
                        if (line.length() + tuple.o1.length() + tuple.o2.length() > maxCols) {
                            //we have to make the wrapping
                            if (breakWithParenthesis) {
                                if (!addedParenForLine) {
                                    line.insert(lastFromXXXImportWritten.length(), '(');
                                    addedParenForLine = true;
                                }
                                line.append(endLineDelim);
                                line.append(indentStr);
                            } else {
                                line.append('\\');
                                line.append(endLineDelim);
                                line.append(indentStr);
                            }
                            all.append(line);
                            line.clear();
                        }
                    }

                    line.append(tuple.o1);

                    if (addedParenForLine && i == importsAndNoComments.size()) {
                        addedParenForLine = false;
                        line.append(')');
                    }

                    firstInLine = false;

                    if (tuple.o2.length() > 0) {
                        if (addedParenForLine) {
                            addedParenForLine = false;
                            line.append(')');
                        }
                        line.append(' ');
                        line.append(tuple.o2);
                        line.append(endLineDelim);
                        all.append(line);
                        line.clear();
                        firstInLine = true;
                    }
                }

                if (!firstInLine) {
                    if (addedParenForLine) {
                        addedParenForLine = false;
                        line.append(')');
                    }
                    line.append(endLineDelim);
                    all.append(line);
                    line.clear();
                }
            }

            writeImportsWithoutFrom(endLineDelim, all, importsWithoutFrom);

        }

        PySelection.addLine(doc, endLineDelim, all.toString(), firstImport);
    }

    /**
     * Write the imports that don't have a 'from' in the beggining (regular imports)
     */
    private static void writeImportsWithoutFrom(String endLineDelim, FastStringBuffer all,
            List<ImportHandleInfo> importsWithoutFrom) {
        //now, write the regular imports (no wrapping or tabbing here)
        for (ImportHandleInfo info : importsWithoutFrom) {

            List<String> importedStr = info.getImportedStr();
            List<String> commentsForImports = info.getCommentsForImports();
            for (int i = 0; i < importedStr.size(); i++) {
                all.append("import ");
                String importedString = importedStr.get(i);
                String comment = commentsForImports.get(i);
                all.append(importedString);
                if (comment.length() > 0) {
                    all.append(' ');
                    all.append(comment);
                }
                all.append(endLineDelim);
            }
        }
    }

    /**
     * Fills the lists passed based on the entry set, so that imports that have comments are contained in a list
     * and imports without comments in another.
     */
    private static void fillImportFromInfo(Entry<String, List<ImportHandleInfo>> entry,
            ArrayList<Tuple<String, String>> importsAndComments, ArrayList<Tuple<String, String>> importsAndNoComments) {
        for (ImportHandleInfo v : entry.getValue()) {
            List<String> importedStr = v.getImportedStr();
            List<String> commentsForImports = v.getCommentsForImports();
            for (int i = 0; i < importedStr.size(); i++) {
                String importedString = importedStr.get(i).trim();
                String comment = commentsForImports.get(i).trim();
                boolean isWildImport = importedString.equals("*");
                if (isWildImport) {
                    importsAndComments.clear();
                    importsAndNoComments.clear();
                }
                if (comment.length() > 0) {
                    importsAndComments.add(new Tuple<String, String>(importedString, comment));
                } else {
                    importsAndNoComments.add(new Tuple<String, String>(importedString, comment));
                }
                if (isWildImport) {
                    return;
                }
            }
        }
    }

    /**
     * @return true if the imports should be split with parenthesis (instead of escaping)
     */
    private static boolean getBreakImportsWithParenthesis() {
        String breakIportMode = ImportsPreferencesPage.getBreakIportMode();
        boolean breakWithParenthesis = true;
        if (!breakIportMode.equals(ImportsPreferencesPage.BREAK_IMPORTS_MODE_PARENTHESIS)) {
            breakWithParenthesis = false;
        }
        return breakWithParenthesis;
    }

    /**
     * @return the maximum number of columns that may be available in a line.
     */
    private static int getMaxCols(boolean multilineImports) {
        int maxCols = 80;
        if (multilineImports) {
            if (PydevPlugin.getDefault() != null) {
                IPreferenceStore chainedPrefStore = PydevPrefs.getChainedPrefStore();
                maxCols = chainedPrefStore
                        .getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN);
            }
        } else {
            maxCols = Integer.MAX_VALUE;
        }
        return maxCols;
    }

    /**
     * Fills the import structure passed, so that the imports from will be grouped by the 'from' part and the regular
     * imports will be in a separate list.
     */
    private static void fillImportStructures(List<Tuple3<Integer, String, ImportHandle>> list,
            TreeMap<String, List<ImportHandleInfo>> importsWithFrom, List<ImportHandleInfo> importsWithoutFrom) {
        //fill the info
        for (Iterator<Tuple3<Integer, String, ImportHandle>> iter = list.iterator(); iter.hasNext();) {
            Tuple3<Integer, String, ImportHandle> element = iter.next();

            List<ImportHandleInfo> importInfo = element.o3.getImportInfo();
            for (ImportHandleInfo importHandleInfo : importInfo) {
                String fromImportStr = importHandleInfo.getFromImportStr();
                if (fromImportStr == null) {
                    importsWithoutFrom.add(importHandleInfo);
                } else {
                    List<ImportHandleInfo> lst = importsWithFrom.get(fromImportStr);
                    if (lst == null) {
                        lst = new ArrayList<ImportHandleInfo>();
                        importsWithFrom.put(fromImportStr, lst);
                    }
                    lst.add(importHandleInfo);
                }
            }
        }
    }

    /**
     * Performs a simple sort without taking into account the actual contents of the selection (aside from lines
     * ending with '\' which are considered as a single line).
     *
     * @param doc the document to be sorted
     * @param endLineDelim the delimiter to be used
     * @param startLine the first line where the sort should happen
     * @param endLine the last line where the sort should happen
     */
    public static void performSimpleSort(IDocument doc, String endLineDelim, int startLine, int endLine) {
        try {
            ArrayList<String> list = new ArrayList<String>();

            StringBuffer lastLine = null;
            for (int i = startLine; i <= endLine; i++) {

                String line = PySelection.getLine(doc, i);

                if (lastLine != null) {
                    int len = lastLine.length();
                    if (len > 0 && lastLine.charAt(len - 1) == '\\') {
                        lastLine.append(endLineDelim);
                        lastLine.append(line);
                    } else {
                        list.add(lastLine.toString());
                        lastLine = new StringBuffer(line);
                    }
                } else {
                    lastLine = new StringBuffer(line);
                }
            }

            if (lastLine != null) {
                list.add(lastLine.toString());
            }

            Collections.sort(list);
            StringBuffer all = new StringBuffer();
            for (Iterator iter = list.iterator(); iter.hasNext();) {
                String element = (String) iter.next();
                all.append(element);
                if (iter.hasNext())
                    all.append(endLineDelim);
            }

            int length = doc.getLineInformation(endLine).getLength();
            int endOffset = doc.getLineInformation(endLine).getOffset() + length;
            int startOffset = doc.getLineInformation(startLine).getOffset();

            doc.replace(startOffset, endOffset - startOffset, all.toString());

        } catch (BadLocationException e) {
            Log.log(e);
        }

    }
}
TOP

Related Classes of org.python.pydev.editor.actions.PyOrganizeImports

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.